import got from 'got'
import { Injectable } from '@nestjs/common'
import { AzureApp, IMsTokenInfo } from 'src/typings'
import { RepositoryService } from './repository.service'

const caches: {
  [key: string]: {
    timer: NodeJS.Timer,
    token: IMsTokenInfo
  }
} = {}

@Injectable()
export class AzureAppOAuthService {

  private readonly repo: RepositoryService<AzureApp>

  constructor() {
    this.repo = new RepositoryService('azure-app.json', 'clientId')
  }

  private getRedirectUrl(clientId: string) {
    return `${process.env.Web_Url}/azure-app-oauth/authorize/callback/${clientId}`
  }

  private updateMsToken(clientId: string, token: IMsTokenInfo) {
    this.repo.updateByPrimaryKey(clientId, { msToken: token })
    if (!caches[clientId]) {
      caches[clientId] = {} as any
    }
    caches[clientId].token = token
  }

  public getTokenFromCache(clientId: string) {
    return caches[clientId] ? caches[clientId].token : null
  }

  public getRefreshTokenStatus(clientId: string) {
    return caches[clientId] != null
  }

  public getAuthUrlByApp(app: AzureApp) {
    let redirectUrl = this.getRedirectUrl(app.clientId)
    let scope = app.oauthScope
    if (!scope.includes('offline_access')) { // 只有包含此权限时，才会返回 refresh_token
      scope += ' offline_access'
    }
    return `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${app.clientId}&scope=${app.oauthScope}&response_type=code&redirect_uri=${redirectUrl}`
  }

  public getAuthUrl(clientId: string) {
    let app = this.repo.findOneOrFail(n => n.clientId === clientId)
    return this.getAuthUrlByApp(app)
  }

  public async oauthCallback(clientId: string, authCode: string) {
    let app = this.repo.findOneOrFail(n => n.clientId === clientId)
    let redirectUrl = this.getRedirectUrl(app.clientId)
    let { body } = await got.post<IMsTokenInfo>('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      form: {
        redirect_uri: redirectUrl,
        client_id: app.clientId,
        client_secret: app.clientSecret,
        code: authCode,
        grant_type: 'authorization_code'
      },
      responseType: 'json'
    })
    body.sign_ts = new Date().getTime()
    body.expires_ts = body.sign_ts + body.expires_in * 1000
    this.updateMsToken(app.clientId, body)
    this.addRefreshTokenTask(app.clientId, body.expires_in / 3 * 1000)
    return body
  }

  public addRefreshTokenTask(clientId: string, interval: number = 20 * 60 * 1000) {
    if (caches[clientId]) {
      clearInterval(caches[clientId].timer)
    } else {
      caches[clientId] = {} as any
    }
    caches[clientId].timer = setInterval(async () => {
      let app = this.repo.findOneOrFail(n => n.clientId === clientId)
      let redirectUrl = this.getRedirectUrl(app.clientId)
      got.post<IMsTokenInfo>('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        form: {
          redirect_uri: redirectUrl,
          client_id: app.clientId,
          client_secret: app.clientSecret,
          refresh_token: app.msToken.refresh_token,
          grant_type: 'refresh_token'
        },
        responseType: 'json'
      }).then(({ body }) => {
        body.sign_ts = new Date().getTime()
        body.expires_ts = body.sign_ts + body.expires_in * 1000
        this.updateMsToken(app.clientId, body)
        console.log('refresh token ok', app.clientId)
      }).catch(res => {
        console.log('refresh token error', app.clientId, res)
      })
    }, interval)
  }

}
